S3イベント通知→SNS→SQSをTerraformで作る
S3イベント通知機能で、S3へのファイルアップロードをSNS→SQSへ通知する処理をTerraformで作成する機会がありましたので、レポートします。
作成するリソースの概要
- もちろんですが、S3、SNS、SQSを作成します。SNSについてはトピックとサブスクリプションが必要です。
- 各リソース間のアクセス許可が必要です。
- S3イベント通知からSNSトピックへイベントを送信するには、SNSトピックのアクセスポリシーでS3がSNSメッセージを発行するのを許可する必要があります。
- SNSからSQSへメッセージを渡すのにも、SQSキューのアクセス許可設定にてSNSからのメッセージ送信を許可する必要があります。
コード内容解説
S3
resource aws_s3_bucket main { bucket_prefix = "event-notification" acl = "private" force_destroy = true } resource aws_s3_bucket_public_access_block main { bucket = aws_s3_bucket.main.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true }
aws_s3_bucket_public_access_block
はS3イベント通知とは関係ありませんが、以下のエントリにあるように基本的に設定しておくべきだと思っているので、設定しています。
SNSトピック
resource aws_sns_topic s3_put_object_notification { name = "event-notification-test-topic" }
特に解説することはありません。キチンとロギングしたいのであれば xxx_success_feedback_role_arn
xxx_failure_feedback_role_arn
xxx_success_feedback_sample_rate
といったargumentを指定してCloudWatch Logsに実行ログを送ることを検討しましょう。(xxxにはサブスクライブするリソースの種類、つまりlambda/sqs/http/applicationが入ります。)
SNSトピックのアクセスポリシー
S3イベント通知からSNSメッセージを発行できるようにするポリシーの設定です。
resource aws_sns_topic_policy from-s3 { arn = aws_sns_topic.s3_put_object_notification.arn policy = templatefile( "./sns_policy_triggered_by_s3_event_notification.json", { sns-arn = aws_sns_topic.s3_put_object_notification.arn, bucket-arn = aws_s3_bucket.main.arn } ) }
{ "Version":"2012-10-17", "Statement":[{ "Effect": "Allow", "Principal": {"Service":"s3.amazonaws.com"}, "Action": "SNS:Publish", "Resource": "${sns-arn}", "Condition":{ "ArnLike":{"aws:SourceArn":"${bucket-arn}"} } }] }
今気づきましたが、リソースベースのポリシーでResource
を指定する(7行目)の、意味ないですね…
S3イベント通知
resource aws_s3_bucket_notification put_object { bucket = aws_s3_bucket.main.bucket topic { topic_arn = aws_sns_topic.s3_put_object_notification.arn events = [ "s3:ObjectCreated:Put", ] } }
ここまでで、S3イベント通知→SNSが実現できました。
以降はSNS→SQSの部分です。
SQSキュー
resource aws_sqs_queue s3_put_object_notification { name = "put-object-notification-queue" visibility_timeout_seconds = 300 message_retention_seconds = 60 * 60 * 24 * 7 * 2 receive_wait_time_seconds = 20 redrive_policy = jsonencode({ deadLetterTargetArn = aws_sqs_queue.s3_put_object_notification_dlq.arn maxReceiveCount = 4 }) } resource aws_sqs_queue s3_put_object_notification_dlq { name = "put-object-notification-dlq" visibility_timeout_seconds = 300 message_retention_seconds = 60 * 60 * 24 * 7 * 2 receive_wait_time_seconds = 20 } resource aws_cloudwatch_metric_alarm dlq_recieved_message { alarm_name = "put-object-notification-dlq-recieved-message" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "1" metric_name = "ApproximateNumberOfMessagesVisible" namespace = "AWS/SQS" period = "60" statistic = "Sum" threshold = "1" alarm_description = "dead letter queue received message" alarm_actions = [aws_sns_topic.dlq_recieved_message_notification.arn] insufficient_data_actions = [] dimensions = { QueueName = aws_sqs_queue.s3_put_object_notification_dlq.name } } resource aws_sns_topic dlq_recieved_message_notification { name = "dlq-recieved-message-notification-topic" }
エラーを補足するためredrive_policy
でデッドレターキューを設定しています。
さらにデッドレターキューにメッセージが入ったことに気づくためCloudWatchAlarm→SNSでメール通知をします。
SNSトピックのEメールサブスクリプションはTerraformでは実装できません。メールでの承認後にしかARNが生成されず、それがTerraformの仕様に合わないためだとのことです。こちらのページのProtocols supported欄最下部に説明があります。そのためEメールサブスクリプションはTerraform外で作成してください。
SQSキューのアクセス許可
SNSにSQSメッセージを送信できる権限を与えます。
resource aws_sqs_queue_policy s3-put-s3_put_object_notification-notification { queue_url = aws_sqs_queue.s3_put_object_notification.id policy = templatefile( "./sqs_policy.json", { sqs-arn = aws_sqs_queue.s3_put_object_notification.arn, sns-arn = aws_sns_topic.s3_put_object_notification.arn } ) }
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "sns.amazonaws.com" }, "Action": "sqs:SendMessage", "Resource": "${sqs-arn}", "Condition": { "ArnEquals": { "aws:SourceArn": "${sns-arn}" } } } ] }
これも別にResource
句要らないですね。
SQSのSNSトピックサブスクリプション
resource aws_sns_topic_subscription sqs { topic_arn = aws_sns_topic.s3_put_object_notification.arn protocol = "sqs" endpoint = aws_sqs_queue.s3_put_object_notification.arn }
このSNSトピックサブスクリプションに対してデッドレターキューを設定することも可能です。が、Terraformは2020年3月末現在(aws provider version 2.55.0)未対応のようです。(おそらくPRはこれ)
SNSトピックサブスクリプションのデッドレターキューについては以下を参照ください。
コード全体
# S3 resource aws_s3_bucket main { bucket_prefix = "event-notification" acl = "private" force_destroy = true } resource aws_s3_bucket_public_access_block main { bucket = aws_s3_bucket.main.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } # SNSトピック resource aws_sns_topic s3_put_object_notification { name = "event-notification-test-topic" } # SNSトピックのアクセスポリシー resource aws_sns_topic_policy from_s3 { arn = aws_sns_topic.s3_put_object_notification.arn policy = templatefile( "./sns_policy_triggered_by_s3_event_notification.json", { sns-arn = aws_sns_topic.s3_put_object_notification.arn, bucket-arn = aws_s3_bucket.main.arn } ) } # S3イベント通知 resource aws_s3_bucket_notification put_object { bucket = aws_s3_bucket.main.bucket topic { topic_arn = aws_sns_topic.s3_put_object_notification.arn events = [ "s3:ObjectCreated:Put", ] } } # SQSキュー resource aws_sqs_queue s3_put_object_notification { name = "put-object-notification-queue" visibility_timeout_seconds = 300 message_retention_seconds = 60 * 60 * 24 * 7 * 2 receive_wait_time_seconds = 20 redrive_policy = jsonencode({ deadLetterTargetArn = aws_sqs_queue.s3_put_object_notification_dlq.arn maxReceiveCount = 4 }) } resource aws_sqs_queue s3_put_object_notification_dlq { name = "put-object-notification-dlq" visibility_timeout_seconds = 300 message_retention_seconds = 60 * 60 * 24 * 7 * 2 receive_wait_time_seconds = 20 } resource aws_cloudwatch_metric_alarm dlq_recieved_message { alarm_name = "put-object-notification-dlq-recieved-message" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "1" metric_name = "ApproximateNumberOfMessagesVisible" namespace = "AWS/SQS" period = "60" statistic = "Sum" threshold = "1" alarm_description = "dead letter queue received message" alarm_actions = [aws_sns_topic.dlq_recieved_message_notification.arn] insufficient_data_actions = [] dimensions = { QueueName = aws_sqs_queue.s3_put_object_notification_dlq.name } } resource aws_sns_topic dlq_recieved_message_notification { name = "dlq-recieved-message-notification-topic" } # SQSキューのアクセス許可 resource aws_sqs_queue_policy s3_put_object_notification { queue_url = aws_sqs_queue.s3_put_object_notification.id policy = templatefile( "./sqs_policy.json", { sqs-arn = aws_sqs_queue.s3_put_object_notification.arn, sns-arn = aws_sns_topic.s3_put_object_notification.arn } ) } # SQSのSNSトピックサブスクリプション resource aws_sns_topic_subscription sqs { topic_arn = aws_sns_topic.s3_put_object_notification.arn protocol = "sqs" endpoint = aws_sqs_queue.s3_put_object_notification.arn }
{ "Version":"2012-10-17", "Statement":[{ "Effect": "Allow", "Principal": {"Service":"s3.amazonaws.com"}, "Action": "SNS:Publish", "Resource": "${sns-arn}", "Condition":{ "ArnLike":{"aws:SourceArn":"${bucket-arn}"} } }] }
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "sns.amazonaws.com" }, "Action": "sqs:SendMessage", "Resource": "${sqs-arn}", "Condition": { "ArnEquals": { "aws:SourceArn": "${sns-arn}" } } } ] }
あわせて読みたい
S3イベント通知とSQSの間にSNSを挟んだ理由は以下をご参照ください。